from __future__ import annotations

"""
用户与账户路由（users）
----------------------
职责：
- 用户基本信息更新
- 账户列表与创建绑定
- 绑定交易所凭据与查询我的已绑定连接器

注意：
- 访问 hummingbot-api 时进行 URL 编码与异常分类处理
- 管理员与普通用户在账户可见范围上有所差异
"""

import logging
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from ..db import get_session
from ..config import get_settings
from urllib.parse import quote
import httpx
from ..deps import require_user
from ..models import AuditLog, MarketType, RoleEnum, User
from ..schemas import (
    AccountBindRequest,
    AccountConnectorBindRequest,
    AccountInfo,
    UserRead,
    UserSettingsUpdate,
)
from ..services.hb_client import HBClientError, hb_client

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/users", tags=["users"])


@router.patch(
    "/me",
    response_model=UserRead,
    summary="更新我的设置",
    description="更新当前用户的通知与全局暂停设置。",
)
async def update_me(
    payload: UserSettingsUpdate,
    current_user=Depends(require_user),
    session: AsyncSession = Depends(get_session),
) -> UserRead:
    """更新当前用户的偏好设置。"""
    user = await session.get(User, current_user.id)
    if user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    if payload.tv_enabled is not None:
        user.tv_enabled = payload.tv_enabled
    if payload.global_pause is not None:
        user.global_pause = payload.global_pause
    session.add(
        AuditLog(
            actor_id=user.id,
            action="user.update_settings",
            target=user.email,
            payload_json=payload.dict(),
        )
    )
    await session.commit()
    await session.refresh(user)
    return UserRead.from_orm(user)


@router.get(
    "/accounts",
    response_model=list[AccountInfo],
    summary="账户列表",
    description="列出 hummingbot-api 中的账户；普通用户仅返回自己的账户，管理员可见全部。",
)
async def list_accounts(
    current_user=Depends(require_user),
) -> list[AccountInfo]:
    """查询账户列表。

    - 管理员：返回全部账户
    - 普通用户：仅返回与自己邮箱同名的账户
    """
    is_admin = current_user.role == RoleEnum.admin if isinstance(current_user.role, RoleEnum) else current_user.role == "admin"
    account_name = current_user.email

    try:
        accounts = await hb_client.get_accounts()
    except HBClientError as exc:
        logger.warning("Failed to fetch accounts from hummingbot-api: %s", exc)
        accounts = []
    raw_items = accounts if isinstance(accounts, list) else accounts.get("accounts", [])

    def _normalize(item: Any) -> tuple[str, Any, Any]:
        if isinstance(item, dict):
            return (
                item.get("name") or item.get("account_name") or "",
                item.get("connector"),
                item.get("status"),
            )
        return str(item), None, None

    normalized = [_normalize(item) for item in raw_items if _normalize(item)[0]]

    if is_admin:
        return [
            AccountInfo(account_name=name, connector=connector, status=status)
            for name, connector, status in normalized
        ]

    matches = [
        (name, connector, status)
        for name, connector, status in normalized
        if name.lower() == account_name.lower()
    ]
    if matches:
        return [AccountInfo(account_name=matches[0][0], connector=matches[0][1], status=matches[0][2])]

    # 未从 hummingbot-api 拉到账号，返回占位信息
    return [AccountInfo(account_name=account_name, connector=None, status="pending")]


@router.post(
    "/accounts/bind",
    response_model=dict,
    summary="创建/绑定交易账户",
    description="为当前用户创建 hummingbot 交易账户（名称等于邮箱），若已存在则返回提示。",
)
async def bind_account(
    payload: AccountBindRequest,
    current_user=Depends(require_user),
    session: AsyncSession = Depends(get_session),
) -> dict:
    """为当前用户创建/绑定 hummingbot 交易账户（名称必须等于邮箱）。"""
    account_name = (payload.account_name or "").strip()
    if not account_name:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="账户名称不能为空")
    if account_name.lower() != current_user.email.lower():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="账户名称必须等于登录邮箱")
    try:
        created = await hb_client.create_account(account_name)
    except HBClientError as exc:
        logger.error("Failed to create account %s via hummingbot-api: %s", account_name, exc)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=str(exc),
        ) from exc

    session.add(
        AuditLog(
            actor_id=current_user.id,
            action="user.bind_account",
            target=account_name,
            payload_json={"account_name": account_name},
        )
    )
    await session.commit()
    if created:
        message = "已为当前用户创建交易账户。"
    else:
        message = "交易账户已存在，无需重复创建。"
    return {"ok": True, "message": message}


@router.post(
    "/accounts/credentials",
    response_model=dict,
    summary="绑定交易所凭据",
    description="为账户绑定一个或多个交易所凭据，支持同时绑定现货与合约。",
)
async def bind_connector_credentials(
    payload: AccountConnectorBindRequest,
    current_user=Depends(require_user),
    session: AsyncSession = Depends(get_session),
) -> dict:
    """为账户绑定一个或多个交易所凭据。

    - 自动补齐多 connector 的字段并校验缺失项
    - 成功/失败分别收集并返回
    """
    account_name = (payload.account_name or "").strip()
    if not account_name:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="账户名称不能为空")
    if account_name.lower() != current_user.email.lower():
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无权操作该交易账户")
    connector_name = (payload.connector_name or "").strip()
    if not connector_name:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="交易所名称不能为空")

    from ..services.credentials_mapping import build_connector_payloads

    try:
        connector_payloads, missing_fields = build_connector_payloads(connector_name, payload.credentials)
    except KeyError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"缺少凭据字段：{exc}") from exc

    try:
        results = await hb_client.add_credentials_multiple(account_name, connector_payloads)
    except HBClientError as exc:
        logger.error(
            "Failed to bind connectors for account %s: %s",
            account_name,
            exc,
        )
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=str(exc),
        ) from exc

    success = [name for name, err in results.items() if err is None]
    failed = {name: err for name, err in results.items() if err}
    for name, fields in missing_fields.items():
        if name not in results:
            failed[name] = f"缺少字段: {', '.join(fields)}"
    if not success:
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail="绑定交易所凭据失败",
        )

    session.add(
        AuditLog(
            actor_id=current_user.id,
            action="user.bind_connector",
            target=f"{account_name}:{'/'.join(sorted(set(list(connector_payloads.keys()) + list(missing_fields.keys()))))}",
            payload_json={
                "account_name": account_name,
                "connectors": list(connector_payloads.keys()),
                "credential_fields": list(payload.credentials.keys()),
                "failed": failed,
            },
        )
    )
    await session.commit()
    message = "成功绑定：" + ", ".join(success)
    if failed:
        message += f"；失败：{failed}"
    return {"ok": True, "message": message, "failed": failed or None}


@router.get(
    "/connectors",
    summary="我的已绑定交易所",
    description="返回当前登录用户已绑定的交易所连接器列表；可按 market_type 过滤（spot/perpetual）。",
)
async def list_my_connectors(
    market_type: MarketType | None = Query(None),
    current_user=Depends(require_user),
) -> dict:
    """返回当前用户已绑定的交易所连接器列表。

    - 优先调用 hb-api /accounts/{account}/credentials
    - 支持按 market_type 过滤现货/合约
    """
    settings = get_settings()
    # 直接调用 hb-api: /accounts/{account_name}/credentials
    try:
        async with httpx.AsyncClient(timeout=10.0) as client:
            base = str(settings.hb_base_url).rstrip('/')
            url = f"{base}/accounts/{quote(current_user.email, safe='')}/credentials"
            resp = await client.get(url, auth=(settings.hb_basic_user, settings.hb_basic_pass))
            if resp.status_code == 404:
                connectors = []
            else:
                resp.raise_for_status()
                data = resp.json()
                if isinstance(data, list):
                    connectors = [str(x) for x in data if x]
                elif isinstance(data, dict) and isinstance(data.get("connectors"), list):
                    connectors = [str(x) for x in data["connectors"] if x]
                else:
                    connectors = []
    except httpx.HTTPStatusError as exc:
        raise HTTPException(status_code=exc.response.status_code, detail="获取连接器失败") from exc
    except httpx.HTTPError as exc:
        # 回退到旧逻辑
        try:
            connectors = await hb_client.list_account_connectors(current_user.email)
        except HBClientError as exc2:
            raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc2)) from exc

    def _is_perp(name: str) -> bool:
        n = name.lower()
        return any(x in n for x in ["perpetual", "_perpetual", "_futures", "_swap"])

    if market_type is not None:
        if market_type == MarketType.perpetual:
            connectors = [c for c in connectors if _is_perp(c)]
        elif market_type == MarketType.spot:
            connectors = [c for c in connectors if not _is_perp(c)]

    unique_sorted = sorted(set(str(c) for c in connectors if c))
    return {"connectors": unique_sorted}


@router.delete(
    "/connectors/{connector_name}",
    summary="解绑我的交易所",
    description="从当前登录账户移除指定交易所的凭据；若连接器不存在则视为已解绑（幂等）。",
)
async def unbind_my_connector(
    connector_name: str,
    current_user=Depends(require_user),
    session: AsyncSession = Depends(get_session),
) -> dict:
    account_name = current_user.email
    try:
        await hb_client.remove_credentials(account_name, connector_name)
    except HBClientError as exc:
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc

    session.add(
        AuditLog(
            actor_id=current_user.id,
            action="user.unbind_connector",
            target=f"{account_name}:{connector_name}",
            payload_json={"account_name": account_name, "connector_name": connector_name},
        )
    )
    await session.commit()
    return {"ok": True}


@router.get(
    "/connectors/{connector_name}/state",
    summary="查询交易所详情（组合状态）",
    description=(
        "参考 hummingbot-api `/portfolio/state`。当前接口返回用户账户维度的组合状态，"
        "其中可能包含多交易所维度的信息；如需细分到某一交易所，前端可据返回结构做筛选展示。"
    ),
)
async def connector_state(
    connector_name: str,
    current_user=Depends(require_user),
) -> dict:
    account_name = current_user.email
    try:
        # 优先查询单交易所组合状态，不支持时回退到账户级并尽力筛选
        portfolio = await hb_client.get_connector_portfolio_state(account_name, connector_name)
        # 若依然为空，则直接回退到账户级组合状态，避免前端拿不到任何数据
        if not portfolio:
            portfolio = await hb_client.get_portfolio_state(account_name)
    except HBClientError as exc:
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc
    return {"connector": connector_name, "portfolio": portfolio or {}}
